#region License, Terms and Conditions
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
#endregion
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using Jayrock;
using Pesta.Libraries.OAuth;
using Pesta.Libraries.OAuth.signature;

namespace raya.Library.OAuth
{
    public class OAuthRequest
    {
        private readonly NameValueCollection parameters;
        private readonly string http_method;
        private readonly string http_url;
        // for debug purposes
        public string base_string;
        public static string version = "1.0";
    
        OAuthRequest(string _http_method, string _http_url, NameValueCollection _parameters)
        {
            parameters = _parameters;
            http_method = _http_method;
            http_url = _http_url;
        }

        /**
     * attempt to build up a request from what was passed to the server
     */
        public static OAuthRequest from_request()
        {
            return from_request(null, null, null);
        }

        public static OAuthRequest from_request(string http_method, string http_url, NameValueCollection parameters)
        {
            HttpRequest httprequest = HttpContext.Current.Request;
            string _scheme = (httprequest.ServerVariables["HTTPS"] != null || httprequest.ServerVariables["HTTPS"] != "on") ? "http" : "https";
            if (String.IsNullOrEmpty(http_url))
            {
                http_url = _scheme + "://" +httprequest.ServerVariables["HTTP_HOST"] + httprequest.ServerVariables["REQUEST_URI"];
            }
            if (String.IsNullOrEmpty(http_method))
            {
                http_method = httprequest.ServerVariables["REQUEST_METHOD"];
            }

            var _request_headers = httprequest.Headers;

            // let the library user override things however they"d like, if they know
            // which parameters to use then go for it, for example XMLRPC might want to
            // do this
            OAuthRequest _req;
            if (parameters != null) 
            {
                _req = new OAuthRequest(http_method, http_url, parameters);
            } 
            else 
            {
                // collect request parameters from query string (GET) and post-data (POST) if appropriate (note: POST vars have priority)
                var _req_parameters = new NameValueCollection(httprequest.QueryString);
                if (http_method == "POST" && _request_headers["Content-Type"] == "application/x-www-form-urlencoded") 
                {
                    _req_parameters.Add(httprequest.Form);
                }

                // next check for the auth header, we need to do some extra stuff
                // if that is the case, namely suck in the parameters from GET or POST
                // so that we can include them in the signature
                if (_request_headers["Authorization"] != null && 
                    _request_headers["Authorization"].Substring(0, 5) == "OAuth")
                {
                    var _header_parameters = split_header(_request_headers["Authorization"]);
                    _req_parameters.Add(_header_parameters);
                    _req = new OAuthRequest(http_method, http_url, _req_parameters);
                } 
                else
                {
                    _req = new OAuthRequest(http_method, http_url, _req_parameters);
                }
            }

            return _req;
        }

        /**
    * pretty much a helper string to set up the request
    */
        public static OAuthRequest from_consumer_and_token(OAuthToken _consumer, OAuthToken _token, string _http_method, string _http_url, ref NameValueCollection _parameters)
        {
            _parameters = new NameValueCollection();
            NameValueCollection _defaults = new NameValueCollection();
            _defaults.Add("oauth_version", version );
            _defaults.Add("oauth_nonce", generate_nonce());
            _defaults.Add("oauth_timestamp", generate_timestamp().ToString());
            _defaults.Add("oauth_consumer_key", _consumer.key);
            _parameters.Add(_defaults);

            if (_token != null)
            {
                _parameters["oauth_token"] = _token.key;
            }
            return new OAuthRequest(_http_method, _http_url, _parameters);
        }

        public void set_parameter(string _name, string _value)
        {
            parameters[_name] = _value;

        }

        public string get_parameter(string _name)
        {
            return parameters[_name];
        }

        public NameValueCollection get_parameters()
        {
            return parameters;
        }


        /**
     * parses the url and rebuilds it to be
     * scheme://host/path
     */
        public string get_normalized_http_url()
        {
            Uri _parts = new Uri(http_url);

            string _port = _parts.Port.ToString();
            string _scheme = _parts.Scheme.ToLower();
            string _host = _parts.Host;
            string _path = _parts.AbsolutePath;

            if (string.IsNullOrEmpty(_port))
            {
                _port = (_scheme == "https") ? "443" : "80";
            }

            if ((_scheme == "https" && _port != "443") || (_scheme == "http" && _port != "80"))
            {
                _host = _host + ":" + _port;
            }
            return _scheme + "://" + _host + _path;
        }

        /**
    * builds a url usable for a GET request
    */
        public string to_url()
        {
            string _out = get_normalized_http_url() + "?";
            _out += to_postdata();
            return _out;
        }

        /**
    * builds the data one would send in a POST request
    */
        public string to_postdata()
        {
            List<string> _total = new List<string>();
            foreach (var key in parameters.AllKeys)
            {
                System.Diagnostics.Debug.Assert(!parameters[key].Contains(","));
                _total.Add(OAuthUtil.urlencodeRFC3986(key) + "=" + OAuthUtil.urlencodeRFC3986(parameters[key]));
            }
            string _out = string.Join("&", _total.ToArray());
            return _out;
        }

        /**
    * builds the Authorization: header
    */
        public string to_header()
        {
            return to_header("");
        }
        public string to_header(string _realm)
        {
            string _out = "Authorization: OAuth realm=\"" + _realm + "\"";
            foreach (var key in parameters.AllKeys)
            {
                if (key.Substring(0, 5) != "oauth")
                    continue;
                System.Diagnostics.Debug.Assert(!parameters[key].Contains(","));
                _out += "," + OAuthUtil.urlencodeRFC3986(key) + "=\"" + OAuthUtil.urlencodeRFC3986(parameters[key]) + "\"";
            }
            return _out;
        }

        public override string ToString()
        {
            return to_url();
        }

        public void sign_request(OAuthSignatureMethod _signature_method, OAuthMessage message)
        {
            set_parameter("oauth_signature_method", _signature_method.GetType().Name);
            _signature_method.sign(message);
        }

        /**
    * util string: current timestamp
    */
        private static double generate_timestamp()
        {
            return UnixTime.ToInt64(DateTime.UtcNow);
        }

        /**
    * util string: current nonce
    */
        private static string generate_nonce()
        {
            int _mt = DateTime.UtcNow.Millisecond;
            Random mt_rand = new Random();
            int _rand = mt_rand.Next(0, _mt);

            return OAuthUtil.md5(_mt + _rand.ToString()); // md5s look nicer than numbers
        }

        /**
     * util string for turning the Authorization: header into
     * _parameters, has to do some unescaping
     */
        private static NameValueCollection split_header(string _header)
        {
            // remove "OAuth " at the start of a header 
            _header = _header.Substring(6);

            // error cases: commas in parameter values?
            string[] _parts = _header.Split(',');
            NameValueCollection _out = new NameValueCollection();
            foreach (var _param in _parts)
            {
                string p = _param.TrimStart();
                // skip the "realm" param, nobody ever uses it anyway
                if (p.Substring(0, 5) != "oauth")
                    continue;

                string[] _param_parts = p.Split('=');

                // rawurldecode() used because HttpUtility.UrlDecode() will turn a "+" in the
                // value into a space
                _out[_param_parts[0]] = Rfc3986.Decode(_param_parts[1].Substring(1, _param_parts[1].Length - 2)).Replace(" ", "+");
            }
            return _out;
        }
    }
}